package me.wz.util.sdbc;

import me.wz.util.Assert;

import java.beans.IntrospectionException;
import java.lang.reflect.Field;
import java.util.*;

public abstract class SQLBuilder {

    //private Map<String, String> cache = new HashMap<String, String>();
    private Map<Class<?>, Metadata> mapp = new HashMap<Class<?>, Metadata>();

    protected abstract Metadata newMetadata(Class<?> clazz) throws IntrospectionException;

    public Metadata getMetadata(Class<?> clazz) throws IntrospectionException {
        if( !mapp.containsKey(clazz) ) {
            synchronized(this) {
                if( !mapp.containsKey(clazz) ) {
                    mapp.put(clazz, newMetadata(clazz));
                }
            }
        }
        return mapp.get(clazz);
    }

    public String insert(Object bean) throws IntrospectionException, IllegalArgumentException, IllegalAccessException {

        Metadata metadata = getMetadata(bean.getClass());

        List<String> columns = new ArrayList<String>();

        List<String> fields = new ArrayList<String>();

        for(String column : metadata.getColumn2field().keySet()) {
            Field field = metadata.getField(column);
            if( field != null ) {
                field.setAccessible(true);
                Object value = field.get(bean);
                if( !Null.isNull(value) ) {
                    String val = ":" + field.getName();
                    String exp = Native.getExpression(value);
                    if( exp != null ) {
                        val = exp;
                    }
                    fields.add(val);
                    columns.add(column);
                }
            }
        }
        String sql = "insert into " + metadata.getTableName() + "(" + join(columns, ", ") + ") values (" + join(fields, ", ") + ")";

        return sql;
    }

    public String delete(Object bean) throws IntrospectionException, IllegalArgumentException, IllegalAccessException {
        Metadata metadata = getMetadata(bean.getClass());
        List<String> wheres = new ArrayList<String>();
        for(Field field : metadata.getFields()) {
            field.setAccessible(true);
            Object value = field.get(bean);
            if( value != null ) {
                Object obj = field.get(bean);
                if( obj != null ) {
                    String val = ":" + field.getName();
                    if( Null.isNull(obj) ) {
                        val = "is null";
                    }
                    else {
                        String exp = Native.getExpression(obj);
                        if( exp != null ) {
                            val = exp;
                        }
                    }
                    wheres.add( metadata.getColumn(field) + " = " + val );
                }
            }
        }
        Assert.notEmpty(wheres, "where can't be empty.");

        String sql = "delete from " + metadata.getTableName() + " where " + join(wheres, " and ");

        return sql;
    }
    public String update(Object bean) throws IntrospectionException, IllegalArgumentException, IllegalAccessException {
        Metadata metadata = getMetadata(bean.getClass());
        List<String> sets = new ArrayList<String>();
        List<String> wheres = new ArrayList<String>();
        for(Field field : metadata.getFields()) {
            field.setAccessible(true);
            Object value = field.get(bean);
            if( value != null ) {
                if( metadata.isId(field) ) {
                    Assert.isTrue(!Null.isNull(field.get(bean)), "where.id can't be NULL");
                    wheres.add( metadata.getColumn(field) + " = :" + field.getName() );
                }
                else {
                    Object obj = field.get(bean);
                    if( obj != null ) {
                        String val = ":" + field.getName();
                        if( Null.isNull(obj) ) {
                            val = "null";
                        }
                        else {
                            String exp = Native.getExpression(obj);
                            if( exp != null ) {
                                val = exp;
                            }
                        }
                        sets.add( metadata.getColumn(field) + " = " + val );
                    }
                }
            }
        }

        Assert.notEmpty(sets, "set can't be empty.");
        Assert.notEmpty(wheres, "where can't be empty.");

        String sql = "update " + metadata.getTableName() + " set " + join(sets, ",") + " where " + join(wheres, " and ");

        return sql;
    }

    public <T>String select(Class<T> clazz) throws IntrospectionException, IllegalArgumentException, IllegalAccessException {
        return "select * from " + getMetadata(clazz).getTableName();
    }
    public String select(Object bean) throws IntrospectionException, IllegalArgumentException, IllegalAccessException {
        Metadata metadata = getMetadata(bean.getClass());
        List<String> wheres = new ArrayList<String>();
        for(Field field : metadata.getFields()) {
            field.setAccessible(true);
            Object value = field.get(bean);
            if( value != null ) {
                wheres.add( metadata.getColumn(field) + (Null.isNull(field.get(bean)) ? " is null " : " = :" + field.getName()) );
            }
        }

        String sql = "select * from " + metadata.getTableName() + (wheres.isEmpty() ? "" : " where " + join(wheres, " and "));

        return sql;
    }

    public String select(Class<?> clazz, Object param) throws IntrospectionException, IllegalArgumentException, IllegalAccessException {

        Metadata metadata = getMetadata(clazz);

        String sql = "select * from " + metadata.getTableName();

        if( param != null ) {
            if( isPrimitive(param) ) {
                Assert.isTrue( metadata.getIdFields().size() == 1, "incorrect pk column parameter: expected 1, actual " + metadata.getIdFields().size() );

                Field field = metadata.getIdFields().get(0);

                sql += " where " + metadata.getColumn(field) + " = ?";
            }
            else {
                List<String> wheres = new ArrayList<String>();
                for(Field field : metadata.getIdFields()){
                    wheres.add(metadata.getColumn(field) + "=:" + field.getName());
                }
                sql += " where " + join(wheres, " and ");
            }
        }
        return sql;
    }

    public static boolean isPrimitive(Class<?> clazz) {
        return String.class.isAssignableFrom(clazz)
                || Integer.class.isAssignableFrom(clazz)
                || Long.class.isAssignableFrom(clazz)
                || Short.class.isAssignableFrom(clazz)
                || Float.class.isAssignableFrom(clazz)
                || Double.class.isAssignableFrom(clazz)
                || Boolean.class.isAssignableFrom(clazz)
                || Byte.class.isAssignableFrom(clazz);
    }
    public static boolean isPrimitive(Object o) {
        return o != null && isPrimitive(o.getClass());
    }
    public static String join(Iterable<?> list, String sep) {
        if(sep == null) {
            sep = ",";
        }
        StringBuffer sb = new StringBuffer();
        for(Iterator<?> iter = list.iterator(); iter.hasNext();){
            sb.append(sep).append(iter.next().toString());
        }
        return sb.length() == 0 ? sb.toString() : sb.delete(0, sep.length()).toString();
    }
}
